package yui.comn.mybatisx.core;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

import org.apache.ibatis.annotations.Lang;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.builder.IncompleteElementException;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder;
import org.apache.ibatis.builder.annotation.MethodResolver;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import yui.comn.mybatisx.annotation.Link;
import yui.comn.mybatisx.core.cache.InjectorCacheAssistant;

/**
 * <p>
 * 通过注解的方式注入基础CRUD方法,和一对一, 一对多连表方法
 * <p>
 * @author yuyi (1060771195@qq.com)
 */
public class InjectorMapperAnnotationBuilder extends MapperAnnotationBuilder {

    private static final Set<Class<? extends Annotation>> statementAnnotationTypes = new HashSet<>();

        private final Configuration configuration;
        private final MapperBuilderAssistant assistant;
        private final Class<?> type;
        private final InjectorMapperAnnotationAssistant injectorMapperAssistant;
        private final InjectorCacheAssistant injectorCacheAssistant;

        public InjectorMapperAnnotationBuilder(Configuration configuration, Class<?> type) {
            super(configuration, type);
            // this.assistant = new MapperBuilderAssistant(configuration, resource);
            // String resource = type.getName().replace('.', '/') + ".java (best guess)";
            if (null != InjectorConfig.MAPPER_ASSISTANT.get(type)) {
            	this.assistant = InjectorConfig.MAPPER_ASSISTANT.get(type);
            } else {
            	String resource = type.getName().replace('.', '/') + ".java (best guess)";
            	this.assistant = new MapperBuilderAssistant(configuration, resource);
            }
            
            this.configuration = configuration;
            this.type = type;
            
            this.injectorMapperAssistant = new InjectorMapperAnnotationAssistant(configuration, assistant, type);
            this.injectorCacheAssistant = new InjectorCacheAssistant(type);
            
            statementAnnotationTypes.add(Link.class);
        }

        @Override
        public void parse() {
            String resource = type.toString();
            if (!InjectorConfig.isInjectorResource(resource)) {
                InjectorConfig.addInjectorResource(resource);
                assistant.setCurrentNamespace(type.getName());
                
                Method[] methods = type.getMethods();
                for (Method method : methods) {
                    try {
                        // issue #237
                        if (!method.isBridge()) {
                            parseStatement2(method);
                        }
                    } catch (IncompleteElementException e) {
                        configuration.addIncompleteMethod(new MethodResolver(this, method));
                    }
                }
            }
        }

        private void parseStatement2(Method method) {
            Class<?> parameterTypeClass = getParameterType(method);
            LanguageDriver languageDriver = getLanguageDriver(method);
            parseAnnotations(method, parameterTypeClass, languageDriver);
        }
        
        private void parseAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
            try {
              Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
              if (sqlAnnotationType != null) {
                  Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
                  Link link = (Link) sqlAnnotation;
                  
                  if (null != link) {
                      //生成resultMap,生成相应的sql
                      injectorMapperAssistant.parse(link, method);
                      injectorCacheAssistant.parse(link, method);
                  }
                  
              } 
            } catch (Exception e) {
              throw new BuilderException("Could not find value method on SQL annotation.  Cause: " + e, e);
            }
        }
        
        private Class<?> getParameterType(Method method) {
            Class<?> parameterType = null;
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (Class<?> currentParameterType : parameterTypes) {
              if (!RowBounds.class.isAssignableFrom(currentParameterType) && !ResultHandler.class.isAssignableFrom(currentParameterType)) {
                if (parameterType == null) {
                  parameterType = currentParameterType;
                } else {
                  // issue #135
                  parameterType = ParamMap.class;
                }
              }
            }
            return parameterType;
        }

        
        private Class<? extends Annotation> getSqlAnnotationType(Method method) {
            return chooseAnnotationType(method, statementAnnotationTypes);
        }

        private Class<? extends Annotation> chooseAnnotationType(Method method, Set<Class<? extends Annotation>> types) {
            for (Class<? extends Annotation> type : types) {
                Annotation annotation = method.getAnnotation(type);
                if (annotation != null) {
                    return type;
                }
            }
            return null;
        }
        
        private LanguageDriver getLanguageDriver(Method method) {
            Lang lang = method.getAnnotation(Lang.class);
            Class<? extends LanguageDriver> langClass = null;
            if (lang != null) {
                langClass = lang.value();
            }
            return configuration.getLanguageDriver(langClass);
        }
        
}
